﻿using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.ComponentModel;
using System.Drawing;
using System.Configuration;
using System.Reflection;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;


using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>
    /// Contains the HTML cascading-style sheets (CSS) inline style attributes for bound <b>DataGrid</b> columns.
    /// </summary>
    /// <remarks>Styles declared for bound columns are applied to each <see cref="DataGridItem"/> object when 
    /// <see cref="DataGrid.ItemCreated"/> event occures.</remarks>
    public sealed class BoundColumnStyleCollection
    {
        #region FieldPermissions.

        DataGrid owner;
        Dictionary<string, Dictionary<string, string>> styleCollections = new Dictionary<string, Dictionary<string, string>>();
        Dictionary<string, Dictionary<string, Dictionary<ListItemType, string>>> typedStyleCollections = new Dictionary<string, Dictionary<string, Dictionary<ListItemType, string>>>();

        #endregion

        #region Constructor.

        internal BoundColumnStyleCollection()
        {
        }

        internal BoundColumnStyleCollection(DataGrid owner)
        {
            this.owner = owner;
            this.owner.ItemCreated += new DataGridItemEventHandler(owner_ItemCreated);
        }

        #endregion

        #region Indexer.

        /// <summary>
        /// Gets or sets a CSS value for the specified column.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to. Case is ignored.</param>
        /// <param name="styleAttribute">CSS style atribute. Eg. "background-color". Case is ignored.</param>
        /// <returns>CSS attribute value. <b>null</b> if column or attribute isn't declared.</returns>
        /// <remarks>Affects all <see cref="ListItemType"/> types.</remarks>
        public string this[string bindingName, string styleAttribute]
        {
            get
            {
                bindingName = bindingName.ToLower();
                styleAttribute = styleAttribute.ToLower();

                string atrValue = null;
                if (this.styleCollections.ContainsKey(bindingName))
                {
                    Dictionary<string, string> colStyle = this.styleCollections[bindingName];
                    if (colStyle.ContainsKey(styleAttribute))
                        atrValue = colStyle[styleAttribute];
                }

                return atrValue;
            }
            set
            {
                bindingName = bindingName.ToLower();
                styleAttribute = styleAttribute.ToLower();

                Dictionary<string, string> colStyle;
                if (this.styleCollections.ContainsKey(bindingName))
                {
                    colStyle = this.styleCollections[bindingName];
                }
                else
                {
                    colStyle = new Dictionary<string, string>();
                    this.styleCollections.Add(bindingName, colStyle);
                }

                if (colStyle.ContainsKey(styleAttribute))
                    colStyle[styleAttribute] = value;
                else
                    colStyle.Add(styleAttribute, value);
            }
        }

        /// <summary>
        /// Gets or sets a CSS value for the specified column.
        /// </summary>
        /// <param name="bindingName">Data field to which a DataGrid column is bound to. Case is ignored.</param>
        /// <param name="styleAttribute">CSS style atribute. Eg. "background-color". Case is ignored.</param>
        /// <param name="itemType">Item type.</param>
        /// <returns>CSS attribute value. <b>null</b> if column, attribute or item type isn't declared.</returns>
        public string this[string bindingName, string styleAttribute, ListItemType itemType]
        {
            get
            {
                bindingName = bindingName.ToLower();
                styleAttribute = styleAttribute.ToLower();

                string atrValue = null;
                if (this.typedStyleCollections.ContainsKey(bindingName))
                {
                    Dictionary<string, Dictionary<ListItemType, string>> typedColAttributes = this.typedStyleCollections[bindingName];
                    if (typedColAttributes.ContainsKey(styleAttribute))
                    {
                        Dictionary<ListItemType, string> itemTypesAndAtrValue = typedColAttributes[styleAttribute];
                        if (itemTypesAndAtrValue.ContainsKey(itemType))
                            atrValue = itemTypesAndAtrValue[itemType];
                    }
                }

                return atrValue;
            }
            set
            {
                bindingName = bindingName.ToLower();
                styleAttribute = styleAttribute.ToLower();

                Dictionary<string, Dictionary<ListItemType, string>> typedColAttributes;
                if (this.typedStyleCollections.ContainsKey(bindingName))
                {
                    typedColAttributes = this.typedStyleCollections[bindingName];
                }
                else
                {
                    typedColAttributes = new Dictionary<string, Dictionary<ListItemType, string>>();
                    this.typedStyleCollections.Add(bindingName, typedColAttributes);
                }

                Dictionary<ListItemType, string> itemTypesAndAtrValue;
                if (typedColAttributes.ContainsKey(styleAttribute))
                {
                    itemTypesAndAtrValue = typedColAttributes[styleAttribute];
                }
                else
                {
                    itemTypesAndAtrValue = new Dictionary<ListItemType, string>();
                    typedColAttributes.Add(styleAttribute, itemTypesAndAtrValue);
                }

                if (itemTypesAndAtrValue.ContainsKey(itemType))
                    itemTypesAndAtrValue[itemType] = value;
                else
                    itemTypesAndAtrValue.Add(itemType, value);
            }
        }

        #endregion

        #region ApplyColumnStylesToGridItem.

        private void owner_ItemCreated(object sender, DataGridItemEventArgs e)
        {
            ApplyColumnStylesToGridItem(e.Item);
        }

        private void ApplyColumnStylesToGridItem(DataGridItem item)
        {
            if (this.cachedColumnIndexMap == null)
                CreateCachedColumnIndexMap(this.owner);

            if (item.Cells.Count != this.owner.Columns.Count)
                return;

            // Attributes for all item types.
            foreach (KeyValuePair<string, Dictionary<string, string>> fieldAndColStyle in this.styleCollections)
            {
                string bindingName = fieldAndColStyle.Key;
                Dictionary<string, string> colStyle = fieldAndColStyle.Value;
                if (this.cachedColumnIndexMap.ContainsKey(bindingName))
                {
                    int cellIdx = this.cachedColumnIndexMap[bindingName];
                    if (cellIdx < item.Cells.Count)
                    {
                        foreach (KeyValuePair<string, string> attributeAndValue in colStyle)
                            item.Cells[cellIdx].Style[attributeAndValue.Key] = attributeAndValue.Value;
                    }
                }
            }

            // Typed attributes.
            foreach (KeyValuePair<string, Dictionary<string, Dictionary<ListItemType, string>>> fieldAtrsTypesVals in this.typedStyleCollections)
            {
                string bindingName = fieldAtrsTypesVals.Key;
                Dictionary<string, Dictionary<ListItemType, string>> atrsTypesVals = fieldAtrsTypesVals.Value;
                if (this.cachedColumnIndexMap.ContainsKey(bindingName))
                {
                    int cellIdx = this.cachedColumnIndexMap[bindingName];
                    if (cellIdx < item.Cells.Count)
                    {
                        foreach (KeyValuePair<string, Dictionary<ListItemType, string>> cssAttributeTypesVals in atrsTypesVals)
                        {
                            string cssAttribute = cssAttributeTypesVals.Key;
                            Dictionary<ListItemType, string> typesVals = cssAttributeTypesVals.Value;
                            foreach (KeyValuePair<ListItemType, string> itemTypeAndVal in typesVals)
                            {
                                ListItemType itemType = itemTypeAndVal.Key;
                                if (item.ItemType == itemType)
                                    item.Cells[cellIdx].Style[cssAttribute] = itemTypeAndVal.Value;
                            }
                        }
                    }
                }
            }
        }

        // Cache field indexes once per postback/control lifecycle to reduce looping.

        Dictionary<string, int> cachedColumnIndexMap;

        private void CreateCachedColumnIndexMap(DataGrid grid)
        {
            this.cachedColumnIndexMap = new Dictionary<string, int>();
            foreach (string boundField in this.styleCollections.Keys)
            {
                int fieldIndex = GetFieldIndex(grid, boundField);
                if (fieldIndex >= 0 && !this.cachedColumnIndexMap.ContainsKey(boundField))
                    this.cachedColumnIndexMap.Add(boundField, fieldIndex);
            }

            foreach (string boundField in this.typedStyleCollections.Keys)
            {
                int fieldIndex = GetFieldIndex(grid, boundField);
                if (fieldIndex >= 0 && !this.cachedColumnIndexMap.ContainsKey(boundField))
                    this.cachedColumnIndexMap.Add(boundField, fieldIndex);
            }
        }

        private static int GetFieldIndex(DataGrid grid, string field)
        {
            for (int idxCol = 0; idxCol < grid.Columns.Count; idxCol++)
            {
                DataGridColumn col = grid.Columns[idxCol];
                if (col is BoundColumn)
                {
                    BoundColumn boundCol = (BoundColumn)col;
                    if (boundCol.DataField.ToLower() == field)
                        return idxCol;
                }
                else if (col is TemplateColumn)
                {
                    // Edit and item templates can be bound to different fields. Prefer edit template.
                    TemplateColumn templateCol = (TemplateColumn)col;
                    BoundTemplateBase boundTemplate;
                    bool hasBoundEditTemplate = (templateCol.EditItemTemplate != null && templateCol.EditItemTemplate is BoundTemplateBase);
                    if (hasBoundEditTemplate)
                        boundTemplate = templateCol.EditItemTemplate as BoundTemplateBase;
                    else
                        boundTemplate = templateCol.ItemTemplate as BoundTemplateBase;

                    if (boundTemplate != null && boundTemplate.BoundField.BindingName.ToLower() == field)
                        return idxCol;
                }
            }

            return -1;
        }

        #endregion

        #region Methods.

        /// <summary>
        /// Duplicates the non-empty style properties of the specified <b>BoundColumnStyleCollection</b> 
        /// into the instance of the <b>BoundColumnStyleCollection</b> class that this method is called from. 
        /// </summary>
        /// <param name="source">A collection that represents the style to copy.</param>
        public void CopyFrom(BoundColumnStyleCollection source)
        {
            // Attributes for all item types.
            foreach (KeyValuePair<string, Dictionary<string, string>> fieldAndColStyle in source.styleCollections)
            {
                string bindingName = fieldAndColStyle.Key;
                if (!this.styleCollections.ContainsKey(bindingName))
                    this.styleCollections.Add(bindingName, new Dictionary<string, string>());

                Dictionary<string, string> sourceColStyle = fieldAndColStyle.Value;
                Dictionary<string, string> targetColStyle = this.styleCollections[bindingName];

                foreach (KeyValuePair<string, string> attributeAndValue in sourceColStyle)
                {
                    if (targetColStyle.ContainsKey(attributeAndValue.Key))
                        targetColStyle[attributeAndValue.Key] = attributeAndValue.Value;
                    else
                        targetColStyle.Add(attributeAndValue.Key, attributeAndValue.Value);
                }
            }

            // Typed attributes.
            foreach (KeyValuePair<string, Dictionary<string, Dictionary<ListItemType, string>>> fieldAtrsTypesVals in source.typedStyleCollections)
            {
                string field = fieldAtrsTypesVals.Key;
                Dictionary<string, Dictionary<ListItemType, string>> sourceAtrsTypesVals = fieldAtrsTypesVals.Value;
                Dictionary<string, Dictionary<ListItemType, string>> targetAtrsTypesVals;
                if (this.typedStyleCollections.ContainsKey(field))
                {
                    targetAtrsTypesVals = this.typedStyleCollections[field];
                }
                else
                {
                    targetAtrsTypesVals = new Dictionary<string, Dictionary<ListItemType, string>>();
                    this.typedStyleCollections.Add(field, targetAtrsTypesVals);
                }

                foreach (KeyValuePair<string, Dictionary<ListItemType, string>> sourceCssAttributeTypeVals in sourceAtrsTypesVals)
                {
                    string cssAttribute = sourceCssAttributeTypeVals.Key;
                    Dictionary<ListItemType, string> sourceTypesVals = sourceCssAttributeTypeVals.Value;
                    Dictionary<ListItemType, string> targetTypesVals;
                    if (targetAtrsTypesVals.ContainsKey(cssAttribute))
                    {
                        targetTypesVals = targetAtrsTypesVals[cssAttribute];
                    }
                    else
                    {
                        targetTypesVals = new Dictionary<ListItemType, string>();
                        targetAtrsTypesVals.Add(cssAttribute, targetTypesVals);
                    }

                    foreach (KeyValuePair<ListItemType, string> sourceItemTypeAndVal in sourceTypesVals)
                    {
                        ListItemType itemType = sourceItemTypeAndVal.Key;
                        string atrValue = sourceItemTypeAndVal.Value;
                        if (targetTypesVals.ContainsKey(itemType))
                            targetTypesVals[itemType] = atrValue;
                        else
                            targetTypesVals.Add(itemType, atrValue);
                    }
                }
            }
        }

        #endregion
    }
}